/*******************************************************************************
 * Copyright (c) Jonathan Buck - jonbuck16@gmail.com
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    jonbuck16@gmail.com - initial API and implementation
 *******************************************************************************/
package net.jonbuck.tassoo.ui.view;

import java.util.ArrayList;

import net.jonbuck.tassoo.TassooPreferenceConstants;
import net.jonbuck.tassoo.eventmanager.EventManager;
import net.jonbuck.tassoo.eventmanager.EventManagerImpl;
import net.jonbuck.tassoo.eventmanager.events.TaskStoreClosedEvent;
import net.jonbuck.tassoo.eventmanager.events.TaskStoreOpenedEvent;
import net.jonbuck.tassoo.eventmanager.listener.TaskStoreClosedListener;
import net.jonbuck.tassoo.eventmanager.listener.TaskStoreOpenedListener;
import net.jonbuck.tassoo.model.Container;
import net.jonbuck.tassoo.model.Task;
import net.jonbuck.tassoo.model.provider.TassooItemProviderAdapterFactory;
import net.jonbuck.tassoo.model.util.TassooAdapterFactory;
import net.jonbuck.tassoo.ui.filter.TaskFilter;
import net.jonbuck.tassoo.ui.nl.Messages;
import net.jonbuck.tassoo.ui.util.ApplicationImages;
import net.jonbuck.tassoo.ui.view.comparator.TasksViewComparator;
import net.jonbuck.tassoo.ui.view.dnd.TasksViewDragListener;
import net.jonbuck.tassoo.ui.view.dnd.TasksViewDropListener;
import net.jonbuck.tassoo.ui.view.model.ColumnData;

import org.apache.commons.lang.StringUtils;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.preferences.ConfigurationScope;
import org.eclipse.core.runtime.preferences.IEclipsePreferences;
import org.eclipse.core.runtime.preferences.IEclipsePreferences.IPreferenceChangeListener;
import org.eclipse.core.runtime.preferences.IEclipsePreferences.PreferenceChangeEvent;
import org.eclipse.core.runtime.preferences.IPreferencesService;
import org.eclipse.emf.edit.ui.provider.AdapterFactoryContentProvider;
import org.eclipse.emf.edit.ui.provider.AdapterFactoryLabelProvider;
import org.eclipse.jface.action.GroupMarker;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.layout.TreeColumnLayout;
import org.eclipse.jface.viewers.CellLabelProvider;
import org.eclipse.jface.viewers.ColumnPixelData;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.TreePath;
import org.eclipse.jface.viewers.TreeSelection;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.TreeViewerColumn;
import org.eclipse.jface.viewers.ViewerCell;
import org.eclipse.swt.SWT;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.TextTransfer;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.TreeColumn;
import org.eclipse.ui.IMemento;
import org.eclipse.ui.ISelectionListener;
import org.eclipse.ui.IViewSite;
import org.eclipse.ui.IWorkbenchActionConstants;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.part.ViewPart;
import org.eclipse.ui.views.properties.IPropertySheetPage;
import org.eclipse.ui.views.properties.tabbed.ITabbedPropertySheetPageContributor;
import org.eclipse.ui.views.properties.tabbed.TabbedPropertySheetPage;
import org.osgi.service.prefs.BackingStoreException;

/**
 * <p>
 * <b>Responsible for displaying the task data to the user.</b>
 * </p>
 * 
 * 
 * @since 1.0.0
 */
public class TasksView extends ViewPart implements ITabbedPropertySheetPageContributor, ISelectionChangedListener,
		TaskStoreOpenedListener, TaskStoreClosedListener, IPreferenceChangeListener {

	/** ... TODO Javadoc ... */
	private static final char DELIMETER = '~';

	/** ... TODO Javadoc ... */
	private TassooAdapterFactory adapterFactory;

	/** ... TODOD Javadoc ... */
	private ColumnData[] columnData;

	/**
	 * <p>
	 * <b>When a column is moved in the user interface this method saves the
	 * column order so that it is persisted between sessions.</b>
	 * </p>
	 */
	private Listener columnMoveListener = new Listener() {
		public void handleEvent(Event event) {
			StringBuilder columnOrder = new StringBuilder();
			for (int columnNumber : treeViewer.getTree().getColumnOrder()) {
				columnOrder.append(columnNumber);
				columnOrder.append(DELIMETER);
			}
			tassooNode.put(TassooPreferenceConstants.TASKS_VIEW_COLUMN_ORDER, columnOrder.toString());
			try {
				tassooNode.flush();
			} catch (BackingStoreException e) {
				// TODO Handle Exception Correctly
				e.printStackTrace();
			}
		}
	};

	/** ... TODO Javadoc ... */
	private Task currentTask;

	/** ... TODO Javadoc ... */
	private EventManager eventManager = EventManagerImpl.getInstance();

	/**
	 * <p>
	 * <b>Listens for changes in the workbench and if the selection is a
	 * <code>Container</code> then sets the input into the view so that it can
	 * display the tasks associated with the selected container.</b
	 * </p>
	 */
	private ISelectionListener selectionListener = new ISelectionListener() {
		public void selectionChanged(IWorkbenchPart part, ISelection selection) {
			if (!(selection instanceof IStructuredSelection)) {
				return;
			}
			IStructuredSelection ss = (IStructuredSelection) selection;
			Object obj = ss.getFirstElement();
			if (obj instanceof Container) {
				treeViewer.setInput(obj);
			}
			setFocus();
		}
	};

	/** ... TODO Javadoc ... */
	private TreeViewer treeViewer;

	/** ... TODO Javadoc ... */
	private IPreferencesService preferenceService;

	/** ... TODO Javadoc ... */
	private TasksViewComparator tasksViewerComparator = new TasksViewComparator();

	/** ... TODO Javadoc ... */
	private Transfer[] transferTypes = new Transfer[] { TextTransfer.getInstance() };

	/** ... TODO Javadoc ... */
	private IEclipsePreferences tassooNode;

	/**
	 * <p>
	 * <b>Default Constructor.</b>
	 * </p>
	 */
	public TasksView() {
		adapterFactory = new TassooItemProviderAdapterFactory();
		preferenceService = Platform.getPreferencesService();
		tassooNode = (IEclipsePreferences) preferenceService.getRootNode().node(ConfigurationScope.SCOPE)
				.node(TassooPreferenceConstants.QUALIFIER);
		tassooNode.addPreferenceChangeListener(this);
	}

	/**
	 * @return the treeViewer
	 */
	public TreeViewer getViewer() {
		return treeViewer;
	}

	/**
	 * @return the columnData
	 */
	public ColumnData[] getColumnData() {
		return columnData;
	}

	/**
	 * @param columnData
	 *            the columnData to set
	 */
	public void setColumnData(ColumnData[] columnData) {
		this.columnData = columnData;
	}

	/**
	 * <p>
	 * <b>Creates the User Interface.</b>
	 * </p>
	 */
	public void createPartControl(Composite parent) {
		Composite composite = new Composite(parent, SWT.NONE);
		TreeColumnLayout tcl_composite = new TreeColumnLayout();
		composite.setLayout(tcl_composite);

		treeViewer = new TreeViewer(composite, SWT.BORDER | SWT.FULL_SELECTION | SWT.VIRTUAL);
		treeViewer.getTree().setHeaderVisible(true);
		treeViewer.getTree().setLinesVisible(true);
		treeViewer.setUseHashlookup(true);
		treeViewer.addDragSupport(DND.DROP_MOVE, transferTypes, new TasksViewDragListener(treeViewer));
		treeViewer.addDropSupport(DND.DROP_MOVE, transferTypes, new TasksViewDropListener(treeViewer));
		treeViewer.setContentProvider(new AdapterFactoryContentProvider(adapterFactory));
		treeViewer.setLabelProvider(new AdapterFactoryLabelProvider.FontAndColorProvider(adapterFactory, treeViewer));
		treeViewer.getTree().forceFocus();
		treeViewer.addFilter(new TaskFilter());
		treeViewer.getTree().addListener(SWT.MeasureItem, new Listener() {
			public void handleEvent(Event event) {
				event.height = 21;
			}
		});
		treeViewer.addSelectionChangedListener(this);
		treeViewer.setComparator(tasksViewerComparator);

		TreeColumn col_TaskName = new TreeColumn(treeViewer.getTree(), SWT.NONE);
		col_TaskName.setText(Messages.tasksview_columntext_taskname);
		col_TaskName.setMoveable(true);
		col_TaskName.addListener(SWT.Move, columnMoveListener);
		col_TaskName.addSelectionListener(getSelectionAdapter(col_TaskName, 0));
		tcl_composite.setColumnData(col_TaskName, new ColumnPixelData(300, true, true));

		TreeColumn col_Category = new TreeColumn(treeViewer.getTree(), SWT.NONE);
		col_Category.setText(Messages.tasksview_columntext_category);
		col_Category.setMoveable(true);
		col_Category.addListener(SWT.Move, columnMoveListener);
		col_Category.addSelectionListener(getSelectionAdapter(col_Category, 1));
		tcl_composite.setColumnData(col_Category, new ColumnPixelData(150, true, true));

		TreeColumn col_Priority = new TreeColumn(treeViewer.getTree(), SWT.NONE);
		col_Priority.setText(Messages.tasksview_columntext_priority);
		col_Priority.setMoveable(true);
		col_Priority.addListener(SWT.Move, columnMoveListener);
		col_Priority.addSelectionListener(getSelectionAdapter(col_Priority, 2));
		tcl_composite.setColumnData(col_Priority, new ColumnPixelData(150, true, true));

		TreeColumn col_Type = new TreeColumn(treeViewer.getTree(), SWT.NONE);
		col_Type.setText(Messages.tasksview_columntext_type);
		col_Type.setMoveable(true);
		col_Type.addListener(SWT.Move, columnMoveListener);
		col_Type.addSelectionListener(getSelectionAdapter(col_Type, 3));
		tcl_composite.setColumnData(col_Type, new ColumnPixelData(150, true, true));

		TreeColumn col_Status = new TreeColumn(treeViewer.getTree(), SWT.NONE);
		col_Status.setText(Messages.tasksview_columntext_status);
		col_Status.setMoveable(true);
		col_Status.addListener(SWT.Move, columnMoveListener);
		col_Status.addSelectionListener(getSelectionAdapter(col_Status, 4));
		tcl_composite.setColumnData(col_Status, new ColumnPixelData(150, true, true));

		TreeViewerColumn col_Progress = new TreeViewerColumn(treeViewer, SWT.NONE);
		col_Progress.getColumn().setText(Messages.tasksview_columntext_progress);
		col_Progress.getColumn().setMoveable(true);
		col_Progress.getColumn().setResizable(false);
		col_Progress.getColumn().addListener(SWT.Move, columnMoveListener);
		col_Progress.getColumn().addSelectionListener(getSelectionAdapter(col_Progress.getColumn(), 5));
		col_Progress.setLabelProvider(new CellLabelProvider() {
			public void update(ViewerCell cell) {
				Task task = (Task) cell.getElement();
				switch (task.getStatus().getAssociatedPercentage()) {
				case 0:
					cell.setImage(null);
					break;
				case 5:
					cell.setImage(ApplicationImages.getImage("icons/progress/bar-5.gif"));
					break;
				case 10:
					cell.setImage(ApplicationImages.getImage("icons/progress/bar-10.gif"));
					break;
				case 15:
					cell.setImage(ApplicationImages.getImage("icons/progress/bar-15.gif"));
					break;
				case 20:
					cell.setImage(ApplicationImages.getImage("icons/progress/bar-20.gif"));
					break;
				case 25:
					cell.setImage(ApplicationImages.getImage("icons/progress/bar-25.gif"));
					break;
				case 30:
					cell.setImage(ApplicationImages.getImage("icons/progress/bar-30.gif"));
					break;
				case 35:
					cell.setImage(ApplicationImages.getImage("icons/progress/bar-35.gif"));
					break;
				case 40:
					cell.setImage(ApplicationImages.getImage("icons/progress/bar-40.gif"));
					break;
				case 45:
					cell.setImage(ApplicationImages.getImage("icons/progress/bar-45.gif"));
					break;
				case 50:
					cell.setImage(ApplicationImages.getImage("icons/progress/bar-50.gif"));
					break;
				case 55:
					cell.setImage(ApplicationImages.getImage("icons/progress/bar-55.gif"));
					break;
				case 60:
					cell.setImage(ApplicationImages.getImage("icons/progress/bar-60.gif"));
					break;
				case 65:
					cell.setImage(ApplicationImages.getImage("icons/progress/bar-65.gif"));
					break;
				case 70:
					cell.setImage(ApplicationImages.getImage("icons/progress/bar-70.gif"));
					break;
				case 75:
					cell.setImage(ApplicationImages.getImage("icons/progress/bar-75.gif"));
					break;
				case 80:
					cell.setImage(ApplicationImages.getImage("icons/progress/bar-80.gif"));
					break;
				case 85:
					cell.setImage(ApplicationImages.getImage("icons/progress/bar-85.gif"));
					break;
				case 90:
					cell.setImage(ApplicationImages.getImage("icons/progress/bar-90.gif"));
					break;
				case 95:
					cell.setImage(ApplicationImages.getImage("icons/progress/bar-95.gif"));
					break;
				default:
					cell.setImage(ApplicationImages.getImage("icons/progress/bar-100.gif"));
				}
			}
		});
		tcl_composite.setColumnData(col_Progress.getColumn(), new ColumnPixelData(124, false, false));

		TreeColumn col_StartDate = new TreeColumn(treeViewer.getTree(), SWT.NONE);
		col_StartDate.setText(Messages.tasksview_columntext_startdate);
		col_StartDate.setMoveable(true);
		col_StartDate.addListener(SWT.Move, columnMoveListener);
		col_StartDate.addSelectionListener(getSelectionAdapter(col_StartDate, 6));
		tcl_composite.setColumnData(col_StartDate, new ColumnPixelData(100, true, true));

		TreeColumn col_DueDate = new TreeColumn(treeViewer.getTree(), SWT.NONE);
		col_DueDate.setText(Messages.tasksview_columntext_duedate);
		col_DueDate.setMoveable(true);
		col_DueDate.addListener(SWT.Move, columnMoveListener);
		col_DueDate.addSelectionListener(getSelectionAdapter(col_DueDate, 7));
		tcl_composite.setColumnData(col_DueDate, new ColumnPixelData(100, true, true));

		TreeColumn col_CompletionDate = new TreeColumn(treeViewer.getTree(), SWT.NONE);
		col_CompletionDate.setText(Messages.tasksview_columntext_completiondate);
		col_CompletionDate.setMoveable(true);
		col_CompletionDate.addListener(SWT.Move, columnMoveListener);
		col_CompletionDate.addSelectionListener(getSelectionAdapter(col_CompletionDate, 8));
		tcl_composite.setColumnData(col_CompletionDate, new ColumnPixelData(100, true, true));

		TreeColumn col_EstimatedTime = new TreeColumn(treeViewer.getTree(), SWT.NONE);
		col_EstimatedTime.setText(Messages.tasksview_columntext_estimatedtime);
		col_EstimatedTime.setMoveable(true);
		col_EstimatedTime.addListener(SWT.Move, columnMoveListener);
		col_EstimatedTime.addSelectionListener(getSelectionAdapter(col_EstimatedTime, 9));
		tcl_composite.setColumnData(col_EstimatedTime, new ColumnPixelData(80, true, true));

		TreeColumn col_ActualTime = new TreeColumn(treeViewer.getTree(), SWT.NONE);
		col_ActualTime.setText(Messages.tasksview_columntext_actualtime);
		col_ActualTime.setMoveable(true);
		col_ActualTime.addListener(SWT.Move, columnMoveListener);
		col_ActualTime.addSelectionListener(getSelectionAdapter(col_ActualTime, 10));
		tcl_composite.setColumnData(col_ActualTime, new ColumnPixelData(80, true, true));

		treeViewer.getTree().setColumnOrder(getColumnOrder());

		MenuManager menuMgr = new MenuManager();
		menuMgr.add(new GroupMarker(IWorkbenchActionConstants.MB_ADDITIONS));
		getSite().registerContextMenu(menuMgr, treeViewer);

		Menu menu = menuMgr.createContextMenu(treeViewer.getControl());
		treeViewer.getControl().setMenu(menu);

		getSite().setSelectionProvider(treeViewer);
		getSite().getPage().addSelectionListener(selectionListener);

	}

	/**
	 * 
	 * @param trclmn
	 * @param position
	 * @return
	 */
	private SelectionListener getSelectionAdapter(final TreeColumn trclmn, int position) {
		return new TaskColumnSelectionAdapter(trclmn, position);
	}

	/**
	 * 
	 * 
	 * @since 1.0.0
	 */
	private class TaskColumnSelectionAdapter extends SelectionAdapter {

		/** ... TODO Javadoc ... */
		private TreeColumn trclmn;

		/** ... TODO Javadoc ... */
		private int position;

		/**
		 * <p>
		 * <b>Default Constructor.</b>
		 * </p>
		 * 
		 * @param trclmn
		 *            the column for which the selection adapter is being
		 *            created
		 * @param pos
		 *            the position of the column in the view
		 */
		public TaskColumnSelectionAdapter(TreeColumn trclmn, int pos) {
			super();
			this.trclmn = trclmn;
			this.position = pos;
		}

		/**
		 * <p>
		 * <b>Determines which way to sort the data when the column is
		 * selected.</b>
		 * </p>
		 * 
		 * @param event
		 *            the <code>SelectionEvent</code> that is generated as a
		 *            result of clicking on the column header.
		 */
		public void widgetSelected(SelectionEvent event) {
			tasksViewerComparator.setColumn(this.position);
			int dir = treeViewer.getTree().getSortDirection();
			switch (dir) {
			case SWT.UP:
				dir = SWT.DOWN;
				break;
			case SWT.DOWN:
				dir = SWT.None;
				break;
			default:
				dir = SWT.UP;
			}
			tasksViewerComparator.setDirection(dir);
			treeViewer.getTree().setSortDirection(dir);
			treeViewer.getTree().setSortColumn(trclmn);
			treeViewer.refresh();
		}

	}

	/**
	 * <p>
	 * <b>Disposes the view when finished.</b>
	 * </p>
	 */
	public void dispose() {
		super.dispose();
	}

	/**
	 * 
	 */
	@SuppressWarnings("rawtypes")
	public Object getAdapter(Class adapter) {
		if (adapter == IPropertySheetPage.class)
			return new TabbedPropertySheetPage(this);
		return super.getAdapter(adapter);
	}

	/**
	 * 
	 * @return
	 */
	private int[] getColumnOrder() {
		int count = 0;
		int[] columnOrder = null;
		String currentColumnOrder[] = StringUtils.split(
				tassooNode.get(TassooPreferenceConstants.TASKS_VIEW_COLUMN_ORDER, "0~1~2~3~4~5~6~7~8~9~10"), DELIMETER); //$NON-NLS-1$
		columnOrder = new int[currentColumnOrder.length];
		for (String number : currentColumnOrder) {
			columnOrder[count] = Integer.parseInt(number);
			count++;
		}
		return columnOrder;
	}

	/**
	 * 
	 */
	public String getContributorId() {
		return getSite().getId();
	}

	/**
	 * <p>
	 * <b>Initializes the view.</b>
	 * </p>
	 * 
	 * TODO Still need to implement view state persistence...
	 */
	public void init(IViewSite site, IMemento memento) throws PartInitException {
		super.init(site, memento);
		eventManager.addListener(TaskStoreOpenedEvent.class, this);
		eventManager.addListener(TaskStoreClosedEvent.class, this);
	}

	/**
	 * <p>
	 * <b>Sets the input for the <code>TreeViewer</code> to be an empty
	 * object.</b>
	 * </p>
	 */
	public void processTaskStoreClosedEvent() {
		treeViewer.setInput(new Object[0]);
	}

	/**
	 * 
	 */
	public void processTaskStoreOpenedEvent() {

	}

	/**
	 * <p>
	 * <b>Sets the <code>currentSelection</code> to be the task currently
	 * selected in the <em>Tasks</em> view.</b>
	 * </p>
	 * 
	 * @param event
	 *            the event associated with the selection changed event.
	 */
	public void selectionChanged(SelectionChangedEvent event) {
		if (event.getSelection().isEmpty()) {
			return;
		}
		IStructuredSelection selection = (IStructuredSelection) event.getSelection();
		Task currentSelection = (Task) selection.getFirstElement();
		if (!currentSelection.equals(currentTask)) {
			this.currentTask = currentSelection;
			TreePath path = new TreePath(new Object[] { this.currentTask });
			treeViewer.setSelection(new TreeSelection(path));
		}
	}

	/**
	 * 
	 */
	public void setFocus() {

	}

	/**
	 * <p>
	 * <b>Updates the columns after the user has chosen which columns to show in
	 * the view.</b>
	 * </p>
	 * 
	 * @param visible
	 *            the list of columns that should be made visible
	 */
	public void updateColumns(ArrayList<ColumnData> columns) {
		for (ColumnData currentColumn : columns) {
			TreeColumn column = treeViewer.getTree().getColumn(currentColumn.getPosition());
			if (currentColumn.isVisible()) {
				column.setWidth(currentColumn.getWidth());
				column.setResizable(true);
			} else {
				column.setWidth(0);
				column.setResizable(false);
			}
		}
		treeViewer.getTree().layout(true);
	}

	/**
	 * 
	 */
	public void preferenceChange(PreferenceChangeEvent event) {
		if (event.getKey().equals("tasks.filter.complete")) {
			treeViewer.refresh();
		}
	}

}
